﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace RattlerRace
{
    /// <summary>
    /// AUTHOR: TADEJ GRUBER
    /// 
    /// To be updated
    /// </summary>
    public class Level
    {
        private Block[,] _game_grid;

        public Block[,] Game_grid
        {
            get { return _game_grid; }
            set { _game_grid = value; }
        }

        private List<Block[,]> _leveli;

        public List<Block[,]> Leveli
        {
            get { return _leveli; }
            set { _leveli = value; }
        }

        private int _level_number;

        public int Level_number
        {
            get { return _level_number; }
            set { _level_number = value; }
        }

        private Wall _stena;

        public Wall Stena
        {
            get { return _stena; }
            set { _stena = value; }
        }

        private List<Snake> _snakes; //dodane kače ©Tomaž Avberšek

        public List<Snake> Snakes
        {
            get { return _snakes; }
            set { _snakes = value; }
        }

        private List<Apple> _apples; //dodana jabolka ©Tomaž Avberšek

        public List<Apple> Apples
        {
            get { return _apples; }
            set { _apples = value; }
        }

        private List<Ball> _balls; //dodane žoge ©Tomaž Avberšek

        public List<Ball> Balls
        {
            get { return _balls; }
            set { _balls = value; }
        }

        private BlankBlock _emptyBlock; //dodani prazni blok ©Tomaž Avberšek

        public BlankBlock EmptyBlock
        {
            get { return _emptyBlock; }
            set { _emptyBlock = value; }
        }
        private LevelCoords _prebran_seznam_levelov;

        public LevelCoords Prebran_seznam_levelov
        {
            get { return _prebran_seznam_levelov; }
            set { _prebran_seznam_levelov = value; }
        }

        /// <summary>
        /// Izbere level iz seznama, ki smo ga napolnili s funkcijo generate
        /// </summary>
        /// <param name="level_num"></param>
        public void setUpLevel(int level_num)
        {
            //napolni seznam
        }

        /// <summary>
        /// Prebere XML datoteko, v kateri so generirane ovire za posamezen Level in shrani le-te v objekt razreda LevelCoords
        /// (zaradi serializera je tako, da imamo dva razreda in zaradi možnosti večih levelov. razložim, ko se dobimo) @Tadej Gruber
        /// </summary>
        /// <param name="path"></param>
        public void readXMLOvire(string path)
        {
            XmlSerializerNamespaces serializer_ns = new XmlSerializerNamespaces();
            serializer_ns.Add("", "");

            TextReader beri_xml = new StreamReader(path);
            _prebran_seznam_levelov = new LevelCoords();
            XmlSerializer serializer = new XmlSerializer(_prebran_seznam_levelov.GetType(), "");
            _prebran_seznam_levelov = (LevelCoords)serializer.Deserialize(beri_xml);
            beri_xml.Close();
        }

        /// <summary>
        /// Prebere xml datoteko z vsemi leveli in vrne generirane levele (vsakega posebej)
        /// Tadej Gruber sprašuje: zakaj vsakega posebej vračat, če pa mamo polje levelov v vsakem tem je gamegrid, kar pomeni, da bi v tej funkciji lahko vse levele prebrali in kar napolnili.
        /// Trenutno samo drugi level naložim
        /// </summary>
        /// <returns></returns>
        public void generate(int levelNum, int snakeNum, int BallNum)
        {
            Block[,] trenutni_level = new Block[41, 41];
            _apples = new List<Apple>();
            _snakes = new List<Snake>();
            if(_leveli == null)
                _leveli = new List<Block[,]>();
            _balls = new List<Ball>();
            _stena = new Wall();
            _stena.State = 2;
            _emptyBlock = new BlankBlock();
            
            readXMLOvire("../../../RattlerRace/Ovire.xml");

            for (int i = 0; i < 41; i++)
            {
                trenutni_level[0, i] = _stena;
                trenutni_level[i, 0] = _stena;
                trenutni_level[40, i] = _stena;
                trenutni_level[i, 40] = _stena;
            }

            for (int i = 1; i < 40; i++)
            {
                for (int j = 1; j < 40; j++)
                {
                    trenutni_level[i, j] = _emptyBlock;
                }
            }
            trenutni_level[40, 20] = _emptyBlock;  //Vrata za izhod

            //polnjenje levelov z ovirami (trenutno samo en level (kao drugi) naredi, ker ne vem kak točno ste si predstavljali, da se level generira)
            for (int i = 0; i < _prebran_seznam_levelov.level_list[levelNum].position.Count; i++)
            {
                trenutni_level[_prebran_seznam_levelov.level_list[levelNum].position[i].X, _prebran_seznam_levelov.level_list[levelNum].position[i].Y] = _stena;
            }
            /*for (int i = 0; i < 10; i++)
                _apples.Add(new Apple(_level_number));*/
            /*for (int i = 0; i < 10; i++)
                _apples.Add(new Apple(_level_number)); tega ne rabimo, to naredi spodnja funkcija*/

            Apple.generateApples(ref _apples, ref trenutni_level);
            _snakes.Clear();
            Snake igralec = new Snake();
            _snakes.Add(igralec);
            
            Snake Hal = new Snake(true, trenutni_level);
            //igralec.Position = new Point(14, 29); //pride not spodaj na sredi
            //igralec.Position = new Point(20, 39);
            //trenutni_level[20, 29] = igralec;

            //_leveli.Clear();
            if (_leveli.Count == levelNum + 1)
                _leveli.RemoveAt(levelNum);
            if (_leveli.Count < levelNum)
                generate(levelNum - 1, snakeNum, BallNum);
            _leveli.Add(trenutni_level);
            changeEnemyNumber(snakeNum);
            changeBallNumber(BallNum);
        }

        /// <summary>
        /// Nova igra za sovražnika ©Tomaž Avberšek
        /// </summary>
        /// <param name="index"></param>
        public void newEnemyGame(int index) //ker je lahko več kač, podaš tisto, ki je bla dovolj nesrečna, da je crknala
        {
            Block[,] trenutniLevel = _leveli[_level_number];
            Snake prev = _snakes[index];
            for(int i=0; i<prev.Body.Count; i++)
            {
                trenutniLevel[prev.Body[i].P.X, prev.Body[i].P.Y] = new BlankBlock();
            }
            _snakes.RemoveAt(index); //zbriši kačo, ki ti pelje sranje
            Snake Hal2 = new Snake(true, trenutniLevel);
            int x = 0, y = 6;
            while(!(trenutniLevel[x, y] is BlankBlock))
            {
                x++;
                if(x > 41)
                {
                    y++;
                    x = 0;
                }
            }
            Hal2.Position = new Point(x, y);
            for(int i=0; i<Hal2.Body.Count; i++)
            {
                Hal2.Body[i].P = new Point(x, y);
                y--;
            }
            _snakes.Add(Hal2);
            _leveli[_level_number] = trenutniLevel;
            //naredi novo
            //daj jo v gameGrid
        }

        private void deleteFromGrid(Snake snake)
        {
            for (int i = 0; i < snake.Body.Count; i++)
                _leveli[_level_number][snake.Body[i].P.X, snake.Body[i].P.Y] = new BlankBlock();
        }

        public void changeEnemyNumber(int num)
        {
            switch(num)
            {
                case 0:
                    for (int i = _snakes.Count - 1; i > 0; i--)
                    {
                        deleteFromGrid(_snakes[i]);
                        _snakes.RemoveAt(i);
                    }
                    break;
                case 1:
                    if (_snakes.Count > 2)
                    {
                        for(int i=_snakes.Count-1; i > 1; i--)
                        {
                            deleteFromGrid(_snakes[i]);
                            _snakes.RemoveAt(i);
                        }
                    }
                    else
                        _snakes.Add(new Snake(true, _leveli[_level_number]));
                    break;
                case 2:
                    if (_snakes.Count > 3)
                    {
                        for (int i = _snakes.Count - 1; i > 2; i--)
                        {
                            deleteFromGrid(_snakes[i]);
                            _snakes.RemoveAt(i);
                        }
                    }
                    else
                        while(_snakes.Count < 3)
                            _snakes.Add(new Snake(true, _leveli[_level_number]));
                    break;
                case 3:
                    while (_snakes.Count < 4)
                        _snakes.Add(new Snake(true, _leveli[_level_number]));
                    break;
            }
        }

        private void removeBallFromGrid(Ball ball)
        {
            _leveli[_level_number][ball.P.X, ball.P.Y] = new BlankBlock();
        }

        public void changeBallNumber(int num)
        {
            int temp_X = 20;
            int temp_y = 5;
            int temp_smer = 4;
            switch(num)
            {
                case 0:
                    for (int i = 0; i < _balls.Count; i++ )
                    {
                        removeBallFromGrid(_balls[i]);
                    }
                    _balls.RemoveRange(0, _balls.Count);
                    break;
                case 1:
                    if(_balls.Count > 1)
                    {
                        for(int i = _balls.Count - 1; i>0; i--)
                        {
                            removeBallFromGrid(_balls[i]);
                            _balls.RemoveAt(i);
                        }
                    }
                    else
                    {
                        temp_X = 20;
                        temp_y = 5;
                        temp_smer = 4;
                        while (_leveli[_level_number][temp_X, temp_y] is Apple || _leveli[_level_number][temp_X, temp_y] is Wall)
                        {
                            temp_X++;
                            temp_y++;
                        }
                        _balls.Add(new Ball(new Point(temp_X, temp_y), temp_smer));
                    }
                    break;
                case 2:
                    temp_X = 20;
                    temp_y = 5;
                    temp_smer = 4;
                    if(_balls.Count > 2)
                    {
                        for(int i = _balls.Count - 1; i>1; i--)
                        {
                            removeBallFromGrid(_balls[i]);
                            _balls.RemoveAt(i);
                        }
                    }
                    else
                    {
                        
                        for(int i=_balls.Count; i<2; i++)
                        {
                            while (_leveli[_level_number][temp_X, temp_y] is Apple || _leveli[_level_number][temp_X, temp_y] is Wall)
                            {
                                temp_X++;
                                temp_y++;
                            }
                            _balls.Add(new Ball(new Point(temp_X, temp_y), temp_smer));
                            temp_smer = temp_smer + 1 < 8 ? temp_smer + 1 : 4;
                        }
                    }
                    break;
                case 3:
                    temp_X = 20;
                    temp_y = 5;
                    temp_smer = 4;
                    for(int i=_balls.Count; i<3; i++)
                    {
                        while (_leveli[_level_number][temp_X, temp_y] is Apple || _leveli[_level_number][temp_X, temp_y] is Wall)
                        {
                            temp_X++;
                            temp_y++;
                        }
                        _balls.Add(new Ball(new Point(temp_X, temp_y), temp_smer));
                        temp_smer = temp_smer + 1 < 8 ? temp_smer + 1 : 4;
                    }
                    break;
            }
        }

        /// <summary>
        /// Dodana metoda, ki posodobi game grid. Kliče privatno metodo matchSnakeWithGrid in vrača 0, če ni bilo posebnosti, 1 če je pojedel igralec kačo in -1 če je prišlo do trka ©Tomaž Avberšek
        /// </summary>
        /// <param name="smer"></param>
        /// <param name="index"></param>
        /// <param name="remove"></param>
        /// <param name="dodaj"></param>
        /// <returns></returns>
        public List<int> updateGameGrid(int smer, ref List<int> dodaj, bool isOpen) //index kače
        {
            //manjka še premik žogice
            List<int> returnNumber = new List<int>();
            for(int i=0; i<_snakes.Count; i++)
                returnNumber.Add(0);
            for (int i = 0; i < _snakes.Count; i++) //zanka ki preišče vse kače
            {
                if (dodaj[i] > 0) //če ima i-ta kača še kej fore, mu doda elemente telesu
                {
                    _snakes[i].addBodyPartsOnAppleEaten(); //dodajamo kvadratek za kvadratkom, ne  vse naenkrat
                    dodaj[i]--; //in zmanjšamo indeks
                }

                Point last = _snakes[i].Body.Last().P; //dobimo položaj zadnjega elementa v telesu
                int rez = 0;
                int dir = -1;
                if (i == 0)
                    rez = matchSnakeWithGrid(i, last, smer, isOpen); //kličemo funkcijo, ki kačo preslika v gameGrdi
                else
                {
                    dir = _snakes[i].AI(_leveli[_level_number]);
                    rez = matchSnakeWithGrid(i, last, dir, isOpen);
                }
                {
                    if (rez == -1) //če je rez == -1, je prišlo do trka
                    {
                        returnNumber[i] = -1;
                        break;
                    }
                    else if (rez == 1) //če je rez == 1, potem je pred kačo jabolko
                    {
                        returnNumber[i] = 1; ; //ker se šele v naslednjem update-u to vidi
                    }
                    else if (rez == 4)
                    {
                        returnNumber[i] = 4;
                    }
                    else
                    {
                        returnNumber[i] = 0; //sicer je BlankBlock
                    }
                }
                {
                    if (i == 0)
                    {
                        _snakes[0].moveSnake(smer); //premaknemo prvo kačo (smer je podana)
                        snakeToGrid(0, last);
                    }
                    else
                    {
                        _snakes[i].moveSnake(dir); //in jo premaknemo v dobljeni smeri
                        snakeToGrid(i, last);
                    }
                }
            }
            Ball.premakniZogico(_leveli[_level_number], ref _balls); //premaknemo žogice
            repairWall(); //ker kača začne izven mape, se stena (wall) pokvari. Zato jo z klicem metode popravimo
            return returnNumber;
        }

        private void repairWall()
        {
            for (int i = 0; i < 41; i++)
            {
                _leveli[_level_number][0, i] = new Wall();
                _leveli[_level_number][i, 0] = new Wall();
                _leveli[_level_number][40, i] = new Wall();
                _leveli[_level_number][i, 40] = new Wall();
            }
        }

        /// <summary>
        /// Privatna metoda, ki premakne kačo, preveri če je prišlo do trka, če se je pojedla jabolka in po potrebi podaljšuje kačo ©Tomaž Avberšek
        /// </summary>
        /// <param name="index"></param>
        /// <param name="last"></param>
        /// <param name="smer"></param>
        /// <returns></returns>
        private int matchSnakeWithGrid(int index, Point last, int smer, bool isOpen)
        {
            int returnNumber = 0;
            Block[,] temp = _leveli[_level_number];
            if(!checkSnake(index))
                return -2;
            int rez = getBlockAhead(smer, index, isOpen); //pogledamo block spredaj
            if (rez == 1) //če je neki spredaj
                return -1; //collision
            else if (rez == 2) // če je rez == 2, pol poje japko
            {
                Point tockaSpredaj = _snakes[index].Body[0].P;
                switch(smer)
                {
                    case 0:
                        tockaSpredaj = new Point(tockaSpredaj.X, tockaSpredaj.Y - 1);
                        break;
                    case 1:
                        tockaSpredaj = new Point(tockaSpredaj.X + 1, tockaSpredaj.Y);
                        break;
                    case 2:
                        tockaSpredaj = new Point(tockaSpredaj.X, tockaSpredaj.Y + 1);
                        break;
                    case 3:
                        tockaSpredaj = new Point(tockaSpredaj.X - 1, tockaSpredaj.Y);
                        break;
                }
                for (int j = 0; j < Apples.Count; j++)
                {
                    if (Apples[j].P == tockaSpredaj)
                    {
                        _apples.RemoveAt(j);
                        break;
                    }
                }
                returnNumber = 1;
            }
            else if(rez == 4)
                return 4;
            switch(smer) //izbrišemo element, ki je pred kačo (predvsem to velja za jabolke)
            {
                case 0:
                    temp[_snakes[index].Body[0].P.X, _snakes[index].Body[0].P.Y - 1] = new BlankBlock();
                    break;
                case 1:
                    temp[_snakes[index].Body[0].P.X + 1, _snakes[index].Body[0].P.Y] = new BlankBlock(); 
                    break;
                case 2:
                    temp[_snakes[index].Body[0].P.X, _snakes[index].Body[0].P.Y + 1] = new BlankBlock();
                    break;
                case 3:
                    temp[_snakes[index].Body[0].P.X - 1, _snakes[index].Body[0].P.Y] = new BlankBlock();
                    break;
            }
            _leveli[_level_number] = temp;
            return returnNumber;
        }

        private void snakeToGrid(int index, Point last)
        {
            Block[,] temp = _leveli[_level_number];
            for (int i = 0; i < _snakes[index].Body.Count; i++) //funkcija namenjena temu, da položaj kače prenese v gameGrid
            {
                temp[_snakes[index].Body[i].P.X, _snakes[index].Body[i].P.Y] = _snakes[index].Body[i];
            }
            temp[last.X, last.Y] = new BlankBlock();
            _leveli[_level_number] = temp;
        }

        /// <summary>
        /// Funkcija, ki preverja, če je kača že cela na mapi (da se izognemo "out of bonds" izjemam)
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private bool checkSnake(int index)
        {
            for(int i=0; i < _snakes[index].Body.Count; i++)
            {
                if (_snakes[index].Body[i].P.X > 41 || _snakes[index].Body[i].P.Y > 41 || _snakes[index].Body[i].P.X < 0 || _snakes[index].Body[i].P.Y < 0)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Pogledamo dejansko preden kačo prestavimo ©Tomaž Avberšek
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private int getBlockAhead(int smer, int index, bool isOpen)
        {
            Block[,] temp = _leveli[_level_number];
            switch(smer)
            {
                case 0:
                    if(_snakes[index].Body[0].P.X == 20 && _snakes[index].Body[0].P.Y - 1 == 0 && isOpen == true)
                        return 4;
                    else if (!(temp[_snakes[index].Body[0].P.X, _snakes[index].Body[0].P.Y - 1] is BlankBlock)) // preveri najprej, če je prazno polje. Če ni, se dodatno preveri, če je spredaj jabolko, sicer je trk
                    {
                        if (temp[_snakes[index].Body[0].P.X, _snakes[index].Body[0].P.Y - 1] is Apple)
                            return 2;
                        return 1;
                    }
                    break;
                case 1:
                    if(_snakes[index].Body[0].P.X + 1 == 20 && _snakes[index].Body[0].P.Y == 0 && isOpen == true)
                        return 4;
                    else if (!(temp[_snakes[index].Body[0].P.X + 1, _snakes[index].Body[0].P.Y] is BlankBlock))
                    {
                        if (temp[_snakes[index].Body[0].P.X + 1, _snakes[index].Body[0].P.Y] is Apple)
                            return 2;
                        return 1;
                    }
                    break;
                case 2:
                    if(_snakes[index].Body[0].P.X == 20 && _snakes[index].Body[0].P.Y + 1 == 0 && isOpen == true)
                        return 4;
                    else if (!(temp[_snakes[index].Body[0].P.X, _snakes[index].Body[0].P.Y + 1] is BlankBlock))
                    {
                        if (temp[_snakes[index].Body[0].P.X, _snakes[index].Body[0].P.Y + 1] is Apple)
                            return 2;
                        return 1;
                    }
                    break;
                case 3:
                    if(_snakes[index].Body[0].P.X - 1 == 20 && _snakes[index].Body[0].P.Y == 0 && isOpen == true)
                        return 4;
                    else if (!(temp[_snakes[index].Body[0].P.X - 1, _snakes[index].Body[0].P.Y] is BlankBlock))
                    {
                        if (temp[_snakes[index].Body[0].P.X - 1, _snakes[index].Body[0].P.Y] is Apple)
                            return 2;
                        return 1;
                    }
                    break;
            }
            return 0;
        }
    }
}
